{{define "dashboard-default/file"}}
<!DOCTYPE html>
<html lang="{{.Conf.Language}}">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>File List</title>
    <link rel="shortcut icon" type="image/png" href="/static/logo.svg?v20210804" />
    <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet" />
    <link rel="stylesheet" href="https://unpkg.com/mdui@2/mdui.css" />
    <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
    <script src="https://unpkg.com/mdui@2/mdui.global.js"></script>
</head>
<style>
    body {
        font-family: 'Roboto', sans-serif;
    }

    .file-list {
        list-style-type: none;
        padding: 0;
    }

    .file-item {
        margin: 5px 0;
        border: 1px solid #ccc;
        border-radius: 5px;
        overflow: hidden;
        text-overflow: ellipsis;
        display: block;
        width: 100%;
        box-sizing: border-box;
    }

    #top-app-bar {
        display: flex;
        align-items: center;
        justify-content: space-between;
        padding: 10px;
    }

    #current-directory {
        font-size: 1rem;
        font-weight: normal;
        margin: 0 15px;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
        max-width: calc(100% - 100px);
        box-sizing: border-box;
    }
</style>

<body>
    <div id="top-app-bar">
        <mdui-dropdown>
            <mdui-button-icon slot="trigger" icon="menu"></mdui-button-icon>
            <mdui-menu>
                <mdui-menu-item id="refresh">{{tr "Refresh"}}</mdui-menu-item>
                <mdui-menu-item id="copy">{{tr "CopyPath"}}</mdui-menu-item>
                <mdui-menu-item id="goto">{{tr "Goto"}}</mdui-menu-item>
            </mdui-menu>
        </mdui-dropdown>
        <span id="current-directory"></span>
        <mdui-button-icon id="upload" icon="upload"></mdui-button-icon>
    </div>

    <mdui-list id="file-list" class="file-list"></mdui-list>

    <mdui-dialog id="error-dialog" headline="Error"
        description="{{tr "FMError"}}"></mdui-dialog>

    <mdui-dialog id="upd-modal" class="modal">
        <mdui-linear-progress id="upd-progress"></mdui-linear-progress>
    </mdui-dialog>

    <mdui-dialog id="goto-dialog" headline="{{tr "GotoHeadline"}}" close-on-overlay-click>
        <mdui-text-field id="goto-text" variant="outlined" value=""></mdui-text-field>
        <mdui-button id="goto-go" slot="action" variant="text">{{tr "GotoGo"}}</mdui-button>
        <mdui-button id="goto-close" slot="action" variant="tonal">{{tr "GotoClose"}}</mdui-button>
    </mdui-dialog>

    <script>
        let currentPath = '/opt/nezha/';
        let fileName = '';
        let receivedBuffer = []; // 用于缓存数据块
        let expectedLength = 0;
        let receivedLength = 0;
        let isFirstChunk = true;
        let isUpCompleted = false;
        let handleReady = false;
        let worker;

        function updateDirectoryTitle() {
            const directoryTitle = document.getElementById('current-directory');
            directoryTitle.textContent = `${currentPath}`;
        }

        function updateFileList(items) {
            const fileListElement = document.getElementById('file-list');
            fileListElement.innerHTML = '';

            if (currentPath !== '/') {
                const upItem = document.createElement('mdui-list-item');
                upItem.className = 'file-item up-directory';
                upItem.setAttribute('icon', 'arrow_back');
                upItem.textContent = "..";
                upItem.onclick = function () {
                    const lastSlashIndex = currentPath.lastIndexOf('/', currentPath.length - 2);
                    currentPath = currentPath.substring(0, lastSlashIndex + 1) || '/';
                    listFile();
                };
                fileListElement.appendChild(upItem);
            }

            items.sort((a, b) => {
                if (a.fileType === 'dir' && b.fileType !== 'dir') {
                    return -1;
                }
                if (a.fileType !== 'dir' && b.fileType === 'dir') {
                    return 1;
                }
                return a.name.localeCompare(b.name);
            });

            items.forEach(item => {
                const listItem = document.createElement('mdui-list-item');
                listItem.className = `file-item ${item.fileType.toLowerCase()}`;
                listItem.setAttribute('nonclickable', 'true');
                listItem.setAttribute('icon', 'insert_drive_file');
                listItem.textContent = `${item.name}`;

                if (item.fileType === 'dir') {
                    listItem.setAttribute('nonclickable', 'false');
                    listItem.setAttribute('icon', 'folder');
                    listItem.style.cursor = 'pointer';
                    listItem.onclick = function () {
                        currentPath += `${item.name}/`;
                        listFile();
                    };
                } else {
                    const downloadButton = document.createElement('mdui-button-icon');
                    downloadButton.setAttribute('slot', 'end-icon');
                    downloadButton.setAttribute('icon', 'download');
                    downloadButton.onclick = function () {
                        const filePath = currentPath + item.name;
                        fileName = item.name;
                        downloadFile(filePath);
                    };
                    listItem.appendChild(downloadButton);
                }

                fileListElement.appendChild(listItem);
            });
            updateDirectoryTitle();
        }

        function resetUpdState() {
            receivedBuffer = [];
            expectedLength = 0;
            receivedLength = 0;
            isFirstChunk = true;
        }

        function downloadFile(filePath) {
            showUpdModal('d');

            const prefix = new Int8Array([1]); // Request download
            const filePathMessage = new TextEncoder().encode(filePath);

            const msg = new Int8Array(prefix.length + filePathMessage.length);
            msg.set(prefix);
            msg.set(filePathMessage, prefix.length);

            socket.send(msg);
        }

        function listFile() {
            const prefix = new Int8Array([0]);
            const resizeMessage = new TextEncoder().encode(currentPath);

            const msg = new Int8Array(prefix.length + resizeMessage.length);
            msg.set(prefix);
            msg.set(resizeMessage, prefix.length);

            socket.send(msg);
        }

        async function uploadFile(file) {
            showUpdModal('u');

            const chunkSize = 1048576; // 1MB chunk
            let offset = 0;

            const filePath = `${currentPath}${file.name}`;
            const fileSize = file.size;
            const messageType = 2;

            // Build header (type + file size + path)
            const filePathBytes = new TextEncoder().encode(filePath);
            const header = new ArrayBuffer(1 + 8 + filePathBytes.length);
            const headerView = new DataView(header);

            headerView.setUint8(0, messageType);
            headerView.setBigUint64(1, BigInt(fileSize), false);

            new Uint8Array(header, 9).set(filePathBytes);

            // Send header
            socket.send(header);

            // Send data chunks
            while (offset < fileSize) {
                const chunk = file.slice(offset, offset + chunkSize);
                const arrayBuffer = await readFileAsArrayBuffer(chunk);
                socket.send(arrayBuffer);
                offset += chunkSize;
            }

            const checkCompletion = setInterval(() => {
                if (isUpCompleted) {
                    clearInterval(checkCompletion);
                    hideUpdModal();
                    resetUpdState();
                    listFile();
                }
            }, 100);
        }

        async function parseFileList(arrayBuffer) {
            const dataView = new DataView(arrayBuffer);
            const items = [];
            let offset = 4;

            const pathLength = dataView.getUint32(offset);
            offset += 4;

            const pathArray = new Uint8Array(arrayBuffer, offset, pathLength);
            currentPath = new TextDecoder('utf-8').decode(pathArray);
            offset += pathLength;

            while (offset < dataView.byteLength) {
                const fileType = dataView.getUint8(offset);
                offset += 1;

                const nameLength = dataView.getUint8(offset);
                offset += 1;

                if (offset + nameLength > dataView.byteLength) {
                    console.error('Error: Name length exceeds buffer size');
                    break;
                }

                const nameArray = new Uint8Array(arrayBuffer, offset, nameLength);
                const name = new TextDecoder('utf-8').decode(nameArray);
                offset += nameLength;

                items.push({
                    fileType: fileType === 0x01 ? 'dir' : 'f',
                    name: name,
                });
            }

            return { items };
        }

        function readFileAsArrayBuffer(blob) {
            return new Promise((resolve, reject) => {
                const reader = new FileReader();
                reader.onload = () => resolve(reader.result);
                reader.onerror = () => reject(reader.error);
                reader.readAsArrayBuffer(blob);
            });
        }

        function concatenateArrayBuffers(buffers) {
            let totalLength = 0;
            buffers.forEach(buf => totalLength += buf.byteLength);

            const result = new Uint8Array(totalLength);
            let offset = 0;
            buffers.forEach(buf => {
                result.set(new Uint8Array(buf), offset);
                offset += buf.byteLength;
            });

            return result.buffer;
        }

        function arraysEqual(a, b) {
            if (a.length !== b.length) return false;
            for (let i = 0; i < a.length; i++) {
                if (a[i] !== b[i]) return false;
            }
            return true;
        }

        async function copyTextToClipboard(text) {
            try {
                await navigator.clipboard.writeText(text);
            } catch (err) {
                console.error('Failed to copy text to clipboard: ', err);
            }
        }

        async function handleError(errMsg) {
            try {
                console.error('Received error: ', errMsg);
                hideUpdModal();
                const errorDialog = document.getElementById('error-dialog');
                errorDialog.open = true;
                if (socket.readyState === WebSocket.OPEN) {
                    socket.close(1000, 'Closing due to error');
                }
            } catch (error) {
                console.error('Error while handling error and closing WebSocket:', error);
            }
        }

        function showUpdModal(operation) {
            const modal = document.getElementById('upd-modal');
            modal.open = true;
            if (operation === 'd') {
                modal.setAttribute('headline', 'Downloading...');
            } else if (operation === 'u') {
                modal.setAttribute('headline', 'Uploading...');
            }
        }

        function hideUpdModal() {
            const modal = document.getElementById('upd-modal');
            modal.open = false;
        }

        function waitForHandleReady() {
            return new Promise(resolve => {
                const checkReady = () => {
                    if (handleReady) {
                        resolve();
                    } else {
                        setTimeout(checkReady, 10);
                    }
                };
                checkReady();
            });
        }

        const socket = new WebSocket((window.location.protocol === 'https:' ? 'wss' : 'ws') + '://' + window.location.host + '/file/' + '{{.SessionID}}');
        socket.binaryType = 'arraybuffer';

        socket.onmessage = async function (event) {
            try {
                const arrayBuffer = event.data;

                if (isFirstChunk) {
                    const identifier = new Uint8Array(arrayBuffer, 0, 4);
                    const fileIdentifier = new Uint8Array([0x4E, 0x5A, 0x54, 0x44]); // NZTD
                    const fileNameIdentifier = new Uint8Array([0x4E, 0x5A, 0x46, 0x4E]); // NZFN
                    const errorIdentifier = new Uint8Array([0x4E, 0x45, 0x52, 0x52]); // NERR
                    const completeIdentifier = new Uint8Array([0x4E, 0x5A, 0x55, 0x50]); // NZUP

                    if (arraysEqual(identifier, fileIdentifier)) {
                        worker = new Worker('/static/file.js');
                        worker.onmessage = async function (event) {
                            switch (event.data.type) {
                                case 'error':
                                    console.error('Error from worker:', event.data.error);
                                    break;
                                case 'progress':
                                    handleReady = true;
                                    break;
                                case 'result':
                                    handleReady = false;
                                    const url = URL.createObjectURL(event.data.blob);
                                    const anchor = document.createElement('a');
                                    anchor.href = url;
                                    anchor.download = event.data.fileName;
                                    anchor.click();
                                    URL.revokeObjectURL(url);

                                    // Delete the file in OPFS
                                    window.addEventListener('beforeunload', async () => {
                                        await worker.postMessage({ operation: 3, arrayBuffer: null, fileName: event.data.fileName });
                                    });

                                    hideUpdModal();
                                    resetUpdState();
                                    break;
                            }
                        };
                        await worker.postMessage({ operation: 1, arrayBuffer: arrayBuffer, fileName: fileName });
                        isFirstChunk = false;
                    } else if (arraysEqual(identifier, fileNameIdentifier)) {
                        // List files
                        const { items } = await parseFileList(arrayBuffer);
                        updateFileList(items);
                        return;
                    } else if (arraysEqual(identifier, errorIdentifier)) {
                        // Handle error
                        const errBytes = arrayBuffer.slice(4);
                        const errMsg = new TextDecoder('utf-8').decode(errBytes);
                        await handleError(errMsg);
                        return;
                    } else if (arraysEqual(identifier, completeIdentifier)) {
                        // Upload is completed
                        isUpCompleted = true;
                        return;
                    } else {
                        console.log('Unknown identifier');
                        return;
                    }
                } else {
                    await waitForHandleReady();
                    await worker.postMessage({ operation: 2, arrayBuffer: arrayBuffer, fileName: fileName });
                }
            } catch (error) {
                console.error('Error processing received data:', error);
            }
        };

        socket.onopen = function (event) {
            listFile();
        };

        socket.onerror = function (event) {
            console.error('WebSocket error:', event);
        };

        socket.onclose = function (event) {
            console.log('WebSocket connection closed:', event);
        };

        document.getElementById('refresh').addEventListener('click', listFile);

        document.getElementById('copy').addEventListener('click', async () => {
            await copyTextToClipboard(currentPath);
        });

        document.getElementById('goto').addEventListener('click', function () {
            const dialog = document.getElementById('goto-dialog');
            const textField = document.getElementById('goto-text');
            const goButton = document.getElementById('goto-go');
            const closeButton = document.getElementById('goto-close');

            dialog.open = true;

            // Ensure the path ends with a separator
            const updateText = function (event) {
                let text = event.target.value;
                if (!text.endsWith('/')) {
                    text += '/';
                }
                return text;
            };

            const handleGoClick = function () {
                let text = updateText({ target: textField });
                currentPath = text;
                listFile();
                dialog.open = false;
            };

            textField.removeEventListener('change', updateText);
            textField.addEventListener('change', updateText);

            goButton.removeEventListener('click', handleGoClick);
            goButton.addEventListener('click', handleGoClick);

            closeButton.addEventListener("click", () => dialog.open = false);
        });

        document.getElementById('upload').addEventListener('click', async function () {
            const fileInput = document.createElement('input');
            fileInput.type = 'file';
            fileInput.style.display = 'none';

            fileInput.addEventListener('change', async function (event) {
                const file = event.target.files[0];
                if (file) {
                    await uploadFile(file);
                    isUpCompleted = false;
                }
            });

            document.body.appendChild(fileInput);
            fileInput.click();
            document.body.removeChild(fileInput);
        });
    </script>
</body>

</html>
{{end}}